Introduction


Data analysis with non-hierarchical clustering (algorithm k-means) on data about line distribution in movies. The analysis was made on the dataset Polygraph’s Film Dialogue. Information about the dataset and how it was generated can be found in its original repository.




Data Overview

readr::read_csv(here("data/character_list5.csv"),
                      progress = FALSE,
                      col_types = cols(
                                    script_id = col_integer(),
                                    imdb_character_name = col_character(),
                                    words = col_integer(),
                                    gender = col_character(),
                                    age = col_character()
                                    )) %>%
  mutate(age = as.numeric(age)) -> characters_list
readr::read_csv(here("data/meta_data7.csv"),
                      progress = FALSE,
         col_types = cols(
                        script_id = col_integer(),
                        imdb_id = col_character(),
                        title = col_character(),
                        year = col_integer(),
                        gross = col_integer(),
                        lines_data = col_character()
                        )) %>%
  mutate(title = iconv(title,"latin1", "UTF-8")) -> meta_data


Combining original data

left_join(characters_list, 
          meta_data, 
          by=c("script_id")) %>%
  group_by(title, year) %>%
  drop_na(gross) %>%
  ungroup() -> scripts_data
scripts_data %>%
  glimpse()
Observations: 19,387
Variables: 10
$ script_id           <int> 280, 280, 280, 280, 280, 280, 280, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623...
$ imdb_character_name <chr> "betty", "carolyn johnson", "eleanor", "francesca johns", "madge", "michael johnson", "robert kincaid",...
$ words               <int> 311, 873, 138, 2251, 190, 723, 1908, 328, 409, 347, 2020, 366, 160, 1337, 1683, 148, 801, 608, 596, 166...
$ gender              <chr> "f", "f", "f", "f", "f", "m", "m", "m", "f", "m", "m", "m", "m", "m", "m", "f", "f", "m", "m", "f", "m"...
$ age                 <dbl> 35, NA, NA, 46, 46, 38, 65, NA, 28, NA, 58, 53, 25, 39, 33, NA, 34, 34, 46, 26, 25, 42, 47, 32, 31, 46,...
$ imdb_id             <chr> "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt0179626",...
$ title               <chr> "The Bridges of Madison County", "The Bridges of Madison County", "The Bridges of Madison County", "The...
$ year                <int> 1995, 1995, 1995, 1995, 1995, 1995, 1995, 2001, 2001, 2001, 2001, 2001, 2001, 2001, 2001, 2001, 2001, 2...
$ gross               <int> 142, 142, 142, 142, 142, 142, 142, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 376, 376, 37...
$ lines_data          <chr> "4332023434343443203433434334433434343434434344344333434443444344233434273", "4332023434343443203433434...
scripts_data %>%
  mutate(fem_words = ifelse(gender == "f",words,0),
         man_words = ifelse(gender == "m",words,0)) %>%
  group_by(title, year) %>%
  mutate(total_fem_words = sum(fem_words),
         total_man_words = sum(man_words)) %>%
  filter(total_fem_words !=  0) %>%
  filter(total_man_words !=  0) %>%
    mutate(f_m_ratio = sum(gender == "f")/sum(gender == "m"),
           f_m_wordratio = total_fem_words/total_man_words) %>%
  ungroup()  -> scripts_data
scripts_data %>%
  select(title,
         year,
         f_m_ratio,
         f_m_wordratio) %>%
  sample_n(10)

Exploring data

Proportion of female and male dialogue

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=f_m_wordratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  labs(y="Relative Frequency",
       x="female/male  wordratio")

  • Em alguns raríssimos exemplos há muito mais dialógo feminino que feminino.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  filter(f_m_wordratio < 10) %>%
  ggplot(aes(x=f_m_wordratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 fill = "grey",
                 color = "black") +
  labs(y="Relative Frequency",
       x="female/male  wordratio")

  • Once the more unusual cases have been filtered we can see a strong dominance of masculine dialogue over feminine dialogue in the movies.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=f_m_wordratio)) +
  geom_violin(fill="grey",
               width=0.5) +
  labs(y="female/male  wordratio") +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank())

  • It becomes even more apparent:
    • The existence of some few cases of complete dominance of feminine dialogue
    • The overall dominance of masculine dialogue over feminine dialogue.

Proportion of female and male characters

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=f_m_ratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  scale_x_continuous(breaks = seq(0,10,0.5)) +
    labs(y="Relative Frequency",
       x="(female chars / male chars) ratio")

  • The predominance of male characters is clear
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=f_m_ratio)) +
  geom_violin(fill="grey",
               width=0.5) +
  labs(y="(female chars / male chars) ratio") +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank())

  • Besides the strong predominance of male characters we can see the existence in some instances however unfrequent of a overwhelming female presence (e.g 10 times more female than male characters).

Year of release

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=year)) +
  geom_bar(fill = "grey",
           color = "black") +
  labs(y="Absolute Frequency",
       x="Year of release")

  • Most of the movies are recent, almost all of them released not before the 1990s.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=year)) +
  geom_violin(fill="grey",
               width=0.5) +
  labs(y="Year of release") +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank())

  • Ainda é possível ver uma presença relevante de filmes do começo dos anos 1980.
  • Existem alguns filmes anteriores aos próprio anos 1950.

Movie revenue

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=gross,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 50,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  labs(y="Relative Frequency", x="Gross")

  • Small or reasonable revenue for most movies.
  • Some few movies had a overwhelming revenue.
scripts_data %>%
  group_by(title,year) %>%   
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=gross)) +
  geom_violin(fill="grey",
               width=0.5) +
  labs(y="Gross") +
  theme(axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank())

  • Similar results to respective histogram.

Putting data on appropriate scale.

scripts_data %>%
  group_by(title) %>%
  slice(1) %>%
  unique() %>%
  ungroup() %>%
  select(title,
         gross,
         f_m_ratio,
         f_m_wordratio) -> data
select(data, -title) %>%
mutate_all(funs(scale)) -> scaled_data
scaled_data %>% 
  sample_n(10)




Optimal K for K-means


GAP Statistic

The GAP Statistic compares the grouping results with each available k in a dataset where there isn’t grouping structure.

plot_clusgap = function(clusgap, title="Gap Statistic calculation results"){
    require("ggplot2")
    gstab = data.frame(clusgap$Tab, k=1:nrow(clusgap$Tab))
    p = ggplot(gstab, aes(k, gap)) + geom_line() + geom_point(size=5)
    p = p + geom_errorbar(aes(ymax=gap+SE.sim, ymin=gap-SE.sim), width = .2)
    p = p + ggtitle(title)
    return(p)
}
gaps <- scaled_data %>% 
    clusGap(FUN = kmeans,
            nstart = 20,
            K.max = 8,
            B = 200,
            iter.max=30)
Clustering k = 1,2,..., K.max (= 8): .. done
Bootstrapping, b = 1,2,..., B (= 200)  [one "." per sample]:
.................................................. 50 
.................................................. 100 
.................................................. 150 
.................................................. 200 
plot_clusgap(gaps)

  • Both 3 groups (K = 3) and 6 groups (K= 6) seem appropriate, but as 6 is preceded by a downward trend 3 would be the choice.

Elbow Method

set.seed(123)
# Compute and plot wss for k = 2 to k = 15.
k.max <- 15
wss <- sapply(1:k.max, 
              function(k){kmeans(scaled_data, k, nstart=50,iter.max = 15 )$tot.withinss})
plot(1:k.max, wss,
     type="b", pch = 19, frame = FALSE, 
     xlab="Number of clusters K",
     ylab="Total within-clusters sum of squares")

  • According to the Elbow method 3 seems a good choice as the drop from 3 to 4 seems substantial.

Bayesian Information Criterion

  • Visually K= 2 e K = 3 both represent the most meaningful gain in terms of BIC (Bayesian Information Criterion)

Hubert Index and D Index

nb <- NbClust(scaled_data, diss=NULL, distance = "euclidean", 
              min.nc=2, max.nc=5, method = "kmeans", 
              index = "all", alphaBeale = 0.1)
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 3 proposed 2 as the best number of clusters 
* 7 proposed 3 as the best number of clusters 
* 11 proposed 4 as the best number of clusters 
* 2 proposed 5 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  4 
 
 
******************************************************************* 

hist(nb$Best.nc[1,], breaks = max(na.omit(nb$Best.nc[1,])))

  • Hubert Index and D Index suggest K = 4 as the best solution




K-Means


Agrupamento

n_clusters = 3
scaled_data %>%
    kmeans(n_clusters, iter.max = 100, nstart = 20) -> km
p <- autoplot(km, data=scaled_data, frame = TRUE)  
ggplotly(p)
  • É possível ver os 3 grupos nitidamente distintos, por meio de um zoom percebe-se que embora o grupo 1 e o grupo 3 estejam próximos o overlap é basicamente inexistente.
row.names(scaled_data) <- data$title
toclust <- scaled_data %>% 
    rownames_to_column(var = "title") 
km = toclust %>% 
    select(-title) %>% 
    kmeans(centers = n_clusters, iter.max = 100, nstart = 20)
km %>% 
    augment(toclust) %>% 
    gather(key = "variável", value = "valor", -title, -.cluster) %>% 
    ggplot(aes(x = `variável`, y = valor, group = title, colour = .cluster)) + 
    geom_point(alpha = 0.2) + 
    geom_line(alpha = .5) + 
    facet_wrap(~ .cluster) +
    coord_flip()



\(\color{red}{\text{Grupo 1}}\) - We Can Do It!

  • Menor Faturamento
  • Mais dialógo para as mulheres
  • Maior taxa de personagens femininos


O \(\color{red}{\text{Grupo 1}}\) - We Can Do It! é o grupo de filmes de maior representação feminina, quer seja em proporção de personagens femininos como em proporção de dialógos dedicados a personagens femininos. Existe porém uma característica negativa que acompanha este mesmo grupo, pois este é também o grupo das menores taxas de faturamento. Isso sugere uma infeliz associação negativa entre a representação feminina em filmes e o faturamento destes.


O nome do grupo se refere ao famoso cartaz de J. Howard Miller de 1943 incentivado as mulheres a participar no esforço de guerra nas fábricas. 



\(\color{green}{\text{Grupo 2}}\) - It’s A Man’s Man’s Man’s World

  • Maior faturamento entre todos
  • Menor taxa de dialógo para as mulheres
  • Menor taxa de personagens femininos


O \(\color{green}{\text{Grupo 2}}\) - It’s A Man’s Man’s Man’s World é o grupo de filmes de menor representação feminina, quer seja em proporção de personagens femininos como em proporção de dialógos dedicados a personagens femininos. Existe porém uma característica negativa que acompanha este mesmo grupo, pois este é também o grupo de maiores taxas de faturamento. Isso sugere uma infeliz associação positiva entre ausência de representação feminina em filmes e o faturamento destes.


O nome do grupo se refere à música de James Brown, a qual foi escrita por sua então namorada Betty Jean Newsome como um comentário sobre a relação entre os sexos.



\(\color{blue}{\text{Grupo 3}}\) - Em cima do muro

  • Filmes medianos em termos de proporção de personagens femininos, proporção de dialógos dedicados a personagens femininos e faturamento.


O nome do grupo se refere à expressão que significa não tomar partido.


Qualidade da clusterização / Silhueta

dists = scaled_data %>% 
  dist()
scaled_data %>%
    kmeans(3, iter.max = 100, nstart = 20) -> km
silhouette(km$cluster, dists) %>%
   plot(col = RColorBrewer::brewer.pal(4, "Set2"),border=NA)


  • O valor de 0.6 da silhueta significa que a nossa clusterização foi razoável. ヾ(⌐■_■)ノ♪
LS0tCnRpdGxlOiAiTGluZSBkaXN0cmlidXRpb24gaW4gbW92aWVzIgpzdWJ0aXRsZTogJ0dlbmRlciBpc3N1ZXMgaW4gTGluZSBkaXN0cmlidXRpb24gaW4gbW92aWVzJwphdXRob3I6ICJKb3PDqSBCZW5hcmRpIGRlIFNvdXphIE51bmVzIgpkYXRlOiAyOS8wNi8yMDE4Cm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCi0tLQoKPGJyPgoKIyBJbnRyb2R1Y3Rpb24KCjxicj4KCj4gRGF0YSBhbmFseXNpcyB3aXRoIG5vbi1oaWVyYXJjaGljYWwgY2x1c3RlcmluZyAoYWxnb3JpdGhtIGstbWVhbnMpIG9uIGRhdGEgYWJvdXQgbGluZSBkaXN0cmlidXRpb24gaW4gbW92aWVzLiBUaGUgYW5hbHlzaXMgd2FzIG1hZGUgb24gdGhlIGRhdGFzZXQgKipQb2x5Z3JhcGgncyBGaWxtIERpYWxvZ3VlKiouIEluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRhc2V0IGFuZCBob3cgaXQgd2FzIGdlbmVyYXRlZCBjYW4gYmUgZm91bmQgaW4gaXRzIFtvcmlnaW5hbCByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vbWF0dGhld2ZkYW5pZWxzL3NjcmlwdHMpLgoKPGJyPgoKKioqCgo8YnI+CgpgYGB7ciBzZXR1cCwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmxpYnJhcnkoaGVyZSkKbGlicmFyeShicm9vbSkKbGlicmFyeSh2ZWdhbikKbGlicmFyeShtY2x1c3QpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KE5iQ2x1c3QpCmxpYnJhcnkobGF0dGljZSkKbGlicmFyeShjbHVzdGVyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ2ZvcnRpZnkpCgp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgojIERhdGEgT3ZlcnZpZXcKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpyZWFkcjo6cmVhZF9jc3YoaGVyZSgiZGF0YS9jaGFyYWN0ZXJfbGlzdDUuY3N2IiksCiAgICAgICAgICAgICAgICAgICAgICBwcm9ncmVzcyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgY29sX3R5cGVzID0gY29scygKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NyaXB0X2lkID0gY29sX2ludGVnZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1kYl9jaGFyYWN0ZXJfbmFtZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd29yZHMgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5kZXIgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFnZSA9IGNvbF9jaGFyYWN0ZXIoKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSAlPiUKICBtdXRhdGUoYWdlID0gYXMubnVtZXJpYyhhZ2UpKSAtPiBjaGFyYWN0ZXJzX2xpc3QKCnJlYWRyOjpyZWFkX2NzdihoZXJlKCJkYXRhL21ldGFfZGF0YTcuY3N2IiksCiAgICAgICAgICAgICAgICAgICAgICBwcm9ncmVzcyA9IEZBTFNFLAogICAgICAgICBjb2xfdHlwZXMgPSBjb2xzKAogICAgICAgICAgICAgICAgICAgICAgICBzY3JpcHRfaWQgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgICBpbWRiX2lkID0gY29sX2NoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgeWVhciA9IGNvbF9pbnRlZ2VyKCksCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3NzID0gY29sX2ludGVnZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXNfZGF0YSA9IGNvbF9jaGFyYWN0ZXIoKQogICAgICAgICAgICAgICAgICAgICAgICApKSAlPiUKICBtdXRhdGUodGl0bGUgPSBpY29udih0aXRsZSwibGF0aW4xIiwgIlVURi04IikpIC0+IG1ldGFfZGF0YQpgYGAKCjxicj4KCiMjIyMgQ29tYmluaW5nIG9yaWdpbmFsIGRhdGEKCmBgYHtyfQpsZWZ0X2pvaW4oY2hhcmFjdGVyc19saXN0LCAKICAgICAgICAgIG1ldGFfZGF0YSwgCiAgICAgICAgICBieT1jKCJzY3JpcHRfaWQiKSkgJT4lCiAgZ3JvdXBfYnkodGl0bGUsIHllYXIpICU+JQogIGRyb3BfbmEoZ3Jvc3MpICU+JQogIHVuZ3JvdXAoKSAtPiBzY3JpcHRzX2RhdGEKCnNjcmlwdHNfZGF0YSAlPiUKICBnbGltcHNlKCkKYGBgCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIG11dGF0ZShmZW1fd29yZHMgPSBpZmVsc2UoZ2VuZGVyID09ICJmIix3b3JkcywwKSwKICAgICAgICAgbWFuX3dvcmRzID0gaWZlbHNlKGdlbmRlciA9PSAibSIsd29yZHMsMCkpICU+JQogIGdyb3VwX2J5KHRpdGxlLCB5ZWFyKSAlPiUKICBtdXRhdGUodG90YWxfZmVtX3dvcmRzID0gc3VtKGZlbV93b3JkcyksCiAgICAgICAgIHRvdGFsX21hbl93b3JkcyA9IHN1bShtYW5fd29yZHMpKSAlPiUKICBmaWx0ZXIodG90YWxfZmVtX3dvcmRzICE9ICAwKSAlPiUKICBmaWx0ZXIodG90YWxfbWFuX3dvcmRzICE9ICAwKSAlPiUKICAgIG11dGF0ZShmX21fcmF0aW8gPSBzdW0oZ2VuZGVyID09ICJmIikvc3VtKGdlbmRlciA9PSAibSIpLAogICAgICAgICAgIGZfbV93b3JkcmF0aW8gPSB0b3RhbF9mZW1fd29yZHMvdG90YWxfbWFuX3dvcmRzKSAlPiUKICB1bmdyb3VwKCkgIC0+IHNjcmlwdHNfZGF0YQoKc2NyaXB0c19kYXRhICU+JQogIHNlbGVjdCh0aXRsZSwKICAgICAgICAgeWVhciwKICAgICAgICAgZl9tX3JhdGlvLAogICAgICAgICBmX21fd29yZHJhdGlvKSAlPiUKICBzYW1wbGVfbigxMCkKYGBgCgojIyBFeHBsb3JpbmcgZGF0YQoKIyMjIFByb3BvcnRpb24gb2YgZmVtYWxlIGFuZCBtYWxlIGRpYWxvZ3VlCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlLHllYXIpICU+JQogIHNsaWNlKDEpICU+JQogIHVuaXF1ZSgpICU+JQogIGdncGxvdChhZXMoeD1mX21fd29yZHJhdGlvLAogICAgICAgICAgICAgeT0oLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuMSwKICAgICAgICAgICAgICAgICBib3VuZGFyeSA9IDAsCiAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmV5IiwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHk9IlJlbGF0aXZlIEZyZXF1ZW5jeSIsCiAgICAgICB4PSJmZW1hbGUvbWFsZSAgd29yZHJhdGlvIikKYGBgCgoqIEVtIGFsZ3VucyByYXLDrXNzaW1vcyBleGVtcGxvcyBow6EgbXVpdG8gbWFpcyBkaWFsw7NnbyBmZW1pbmlubyBxdWUgZmVtaW5pbm8uIAoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBmaWx0ZXIoZl9tX3dvcmRyYXRpbyA8IDEwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Zl9tX3dvcmRyYXRpbywKICAgICAgICAgICAgIHk9KC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEsCiAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmV5IiwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHk9IlJlbGF0aXZlIEZyZXF1ZW5jeSIsCiAgICAgICB4PSJmZW1hbGUvbWFsZSAgd29yZHJhdGlvIikKYGBgCgoqIE9uY2UgdGhlIG1vcmUgdW51c3VhbCBjYXNlcyBoYXZlIGJlZW4gZmlsdGVyZWQgd2UgY2FuIHNlZSBhIHN0cm9uZyBkb21pbmFuY2Ugb2YgbWFzY3VsaW5lIGRpYWxvZ3VlIG92ZXIgZmVtaW5pbmUgZGlhbG9ndWUgaW4gdGhlIG1vdmllcy4KCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PSIiLCAKICAgICAgICAgICAgIHk9Zl9tX3dvcmRyYXRpbykpICsKICBnZW9tX3Zpb2xpbihmaWxsPSJncmV5IiwKICAgICAgICAgICAgICAgd2lkdGg9MC41KSArCiAgbGFicyh5PSJmZW1hbGUvbWFsZSAgd29yZHJhdGlvIikgKwogIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpCmBgYAoKKiBJdCBiZWNvbWVzIGV2ZW4gbW9yZSBhcHBhcmVudDoKICAgICogVGhlIGV4aXN0ZW5jZSBvZiBzb21lIGZldyBjYXNlcyBvZiBjb21wbGV0ZSBkb21pbmFuY2Ugb2YgZmVtaW5pbmUgZGlhbG9ndWUKICAgICogVGhlIG92ZXJhbGwgZG9taW5hbmNlIG9mIG1hc2N1bGluZSBkaWFsb2d1ZSBvdmVyIGZlbWluaW5lIGRpYWxvZ3VlLgoKIyMjIFByb3BvcnRpb24gb2YgZmVtYWxlIGFuZCBtYWxlIGNoYXJhY3RlcnMKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PWZfbV9yYXRpbywKICAgICAgICAgICAgIHk9KC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEsCiAgICAgICAgICAgICAgICAgYm91bmRhcnkgPSAwLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDEwLDAuNSkpICsKICAgIGxhYnMoeT0iUmVsYXRpdmUgRnJlcXVlbmN5IiwKICAgICAgIHg9IihmZW1hbGUgY2hhcnMgLyBtYWxlIGNoYXJzKSByYXRpbyIpCmBgYAoKKiBUaGUgcHJlZG9taW5hbmNlIG9mIG1hbGUgY2hhcmFjdGVycyBpcyBjbGVhcgoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IiIsIAogICAgICAgICAgICAgeT1mX21fcmF0aW8pKSArCiAgZ2VvbV92aW9saW4oZmlsbD0iZ3JleSIsCiAgICAgICAgICAgICAgIHdpZHRoPTAuNSkgKwogIGxhYnMoeT0iKGZlbWFsZSBjaGFycyAvIG1hbGUgY2hhcnMpIHJhdGlvIikgKwogIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpCmBgYAoKKiBCZXNpZGVzIHRoZSBzdHJvbmcgcHJlZG9taW5hbmNlIG9mIG1hbGUgY2hhcmFjdGVycyB3ZSBjYW4gc2VlIHRoZSBleGlzdGVuY2UgaW4gc29tZSBpbnN0YW5jZXMgaG93ZXZlciB1bmZyZXF1ZW50IG9mIGEgb3ZlcndoZWxtaW5nIGZlbWFsZSBwcmVzZW5jZSAoZS5nIDEwIHRpbWVzIG1vcmUgZmVtYWxlIHRoYW4gbWFsZSBjaGFyYWN0ZXJzKS4KCiMjIyBZZWFyIG9mIHJlbGVhc2UKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PXllYXIpKSArCiAgZ2VvbV9iYXIoZmlsbCA9ICJncmV5IiwKICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHk9IkFic29sdXRlIEZyZXF1ZW5jeSIsCiAgICAgICB4PSJZZWFyIG9mIHJlbGVhc2UiKQpgYGAKCiogTW9zdCBvZiB0aGUgbW92aWVzIGFyZSByZWNlbnQsIGFsbW9zdCBhbGwgb2YgdGhlbSByZWxlYXNlZCBub3QgYmVmb3JlIHRoZSAxOTkwcy4KCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PSIiLCAKICAgICAgICAgICAgIHk9eWVhcikpICsKICBnZW9tX3Zpb2xpbihmaWxsPSJncmV5IiwKICAgICAgICAgICAgICAgd2lkdGg9MC41KSArCiAgbGFicyh5PSJZZWFyIG9mIHJlbGVhc2UiKSArCiAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkKYGBgCgoqIEFpbmRhIMOpIHBvc3PDrXZlbCB2ZXIgdW1hIHByZXNlbsOnYSByZWxldmFudGUgZGUgZmlsbWVzIGRvIGNvbWXDp28gZG9zIGFub3MgMTk4MC4KKiBFeGlzdGVtIGFsZ3VucyBmaWxtZXMgYW50ZXJpb3JlcyBhb3MgcHLDs3ByaW8gYW5vcyAxOTUwLgoKIyMjIE1vdmllIHJldmVudWUKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PWdyb3NzLAogICAgICAgICAgICAgeT0oLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUwLAogICAgICAgICAgICAgICAgIGJvdW5kYXJ5ID0gMCwKICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeT0iUmVsYXRpdmUgRnJlcXVlbmN5IiwgeD0iR3Jvc3MiKQpgYGAKCiogU21hbGwgb3IgcmVhc29uYWJsZSByZXZlbnVlIGZvciBtb3N0IG1vdmllcy4KKiBTb21lIGZldyBtb3ZpZXMgaGFkIGEgb3ZlcndoZWxtaW5nIHJldmVudWUuCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlLHllYXIpICU+JSAgIAogIHNsaWNlKDEpICU+JQogIHVuaXF1ZSgpICU+JQogIGdncGxvdChhZXMoeD0iIiwgCiAgICAgICAgICAgICB5PWdyb3NzKSkgKwogIGdlb21fdmlvbGluKGZpbGw9ImdyZXkiLAogICAgICAgICAgICAgICB3aWR0aD0wLjUpICsKICBsYWJzKHk9Ikdyb3NzIikgKwogIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpCmBgYAoKKiBTaW1pbGFyIHJlc3VsdHMgdG8gcmVzcGVjdGl2ZSBoaXN0b2dyYW0uCgojIyBQdXR0aW5nIGRhdGEgb24gYXBwcm9wcmlhdGUgc2NhbGUuCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KHRpdGxlLAogICAgICAgICBncm9zcywKICAgICAgICAgZl9tX3JhdGlvLAogICAgICAgICBmX21fd29yZHJhdGlvKSAtPiBkYXRhCgpzZWxlY3QoZGF0YSwgLXRpdGxlKSAlPiUKbXV0YXRlX2FsbChmdW5zKHNjYWxlKSkgLT4gc2NhbGVkX2RhdGEKCnNjYWxlZF9kYXRhICU+JSAKICBzYW1wbGVfbigxMCkKYGBgCgo8YnI+CgoqKioKCjxicj4KCiMgIE9wdGltYWwgSyBmb3IgSy1tZWFucwoKPGJyPgoKIyMgR0FQIFN0YXRpc3RpYwoKVGhlIEdBUCBTdGF0aXN0aWMgY29tcGFyZXMgdGhlIGdyb3VwaW5nIHJlc3VsdHMgd2l0aCBlYWNoIGF2YWlsYWJsZSBrIGluIGEgZGF0YXNldCB3aGVyZSB0aGVyZSBpc24ndCBncm91cGluZyBzdHJ1Y3R1cmUuIAoKYGBge3J9CnBsb3RfY2x1c2dhcCA9IGZ1bmN0aW9uKGNsdXNnYXAsIHRpdGxlPSJHYXAgU3RhdGlzdGljIGNhbGN1bGF0aW9uIHJlc3VsdHMiKXsKICAgIHJlcXVpcmUoImdncGxvdDIiKQogICAgZ3N0YWIgPSBkYXRhLmZyYW1lKGNsdXNnYXAkVGFiLCBrPTE6bnJvdyhjbHVzZ2FwJFRhYikpCiAgICBwID0gZ2dwbG90KGdzdGFiLCBhZXMoaywgZ2FwKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoc2l6ZT01KQogICAgcCA9IHAgKyBnZW9tX2Vycm9yYmFyKGFlcyh5bWF4PWdhcCtTRS5zaW0sIHltaW49Z2FwLVNFLnNpbSksIHdpZHRoID0gLjIpCiAgICBwID0gcCArIGdndGl0bGUodGl0bGUpCiAgICByZXR1cm4ocCkKfQpgYGAKCmBgYHtyfQpnYXBzIDwtIHNjYWxlZF9kYXRhICU+JSAKICAgIGNsdXNHYXAoRlVOID0ga21lYW5zLAogICAgICAgICAgICBuc3RhcnQgPSAyMCwKICAgICAgICAgICAgSy5tYXggPSA4LAogICAgICAgICAgICBCID0gMjAwLAogICAgICAgICAgICBpdGVyLm1heD0zMCkKYGBgCgpgYGB7cn0KcGxvdF9jbHVzZ2FwKGdhcHMpCmBgYAoKKiBCb3RoIDMgZ3JvdXBzIChLID0gMykgYW5kIDYgZ3JvdXBzIChLPSA2KSBzZWVtIGFwcHJvcHJpYXRlLCBidXQgYXMgNiBpcyBwcmVjZWRlZCBieSBhIGRvd253YXJkIHRyZW5kIDMgd291bGQgYmUgdGhlIGNob2ljZS4KCiMjIEVsYm93IE1ldGhvZAoKYGBge3J9CnNldC5zZWVkKDEyMykKIyBDb21wdXRlIGFuZCBwbG90IHdzcyBmb3IgayA9IDIgdG8gayA9IDE1LgprLm1heCA8LSAxNQoKd3NzIDwtIHNhcHBseSgxOmsubWF4LCAKICAgICAgICAgICAgICBmdW5jdGlvbihrKXtrbWVhbnMoc2NhbGVkX2RhdGEsIGssIG5zdGFydD01MCxpdGVyLm1heCA9IDE1ICkkdG90LndpdGhpbnNzfSkKcGxvdCgxOmsubWF4LCB3c3MsCiAgICAgdHlwZT0iYiIsIHBjaCA9IDE5LCBmcmFtZSA9IEZBTFNFLCAKICAgICB4bGFiPSJOdW1iZXIgb2YgY2x1c3RlcnMgSyIsCiAgICAgeWxhYj0iVG90YWwgd2l0aGluLWNsdXN0ZXJzIHN1bSBvZiBzcXVhcmVzIikKYGBgCgoqIEFjY29yZGluZyB0byB0aGUgRWxib3cgbWV0aG9kIDMgc2VlbXMgYSBnb29kIGNob2ljZSBhcyB0aGUgZHJvcCBmcm9tIDMgdG8gNCBzZWVtcyBzdWJzdGFudGlhbC4KCiMjIEJheWVzaWFuIEluZm9ybWF0aW9uIENyaXRlcmlvbgoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZF9jbHVzdCA8LSBNY2x1c3QoYXMubWF0cml4KHNjYWxlZF9kYXRhKSwgRz0xOjE1LAogICAgICAgICAgICAgICAgICBtb2RlbE5hbWVzID0gbWNsdXN0Lm9wdGlvbnMoImVtTW9kZWxOYW1lcyIpKQoKcGxvdChkX2NsdXN0JEJJQykKYGBgCgoqIFZpc3VhbGx5IEs9IDIgZSBLID0gMyBib3RoIHJlcHJlc2VudCB0aGUgbW9zdCBtZWFuaW5nZnVsIGdhaW4gaW4gdGVybXMgb2YgQklDIChCYXllc2lhbiBJbmZvcm1hdGlvbiBDcml0ZXJpb24pIAoKIyMgSHViZXJ0IEluZGV4IGFuZCBEIEluZGV4CgpgYGB7cn0KbmIgPC0gTmJDbHVzdChzY2FsZWRfZGF0YSwgZGlzcz1OVUxMLCBkaXN0YW5jZSA9ICJldWNsaWRlYW4iLCAKICAgICAgICAgICAgICBtaW4ubmM9MiwgbWF4Lm5jPTUsIG1ldGhvZCA9ICJrbWVhbnMiLCAKICAgICAgICAgICAgICBpbmRleCA9ICJhbGwiLCBhbHBoYUJlYWxlID0gMC4xKQpoaXN0KG5iJEJlc3QubmNbMSxdLCBicmVha3MgPSBtYXgobmEub21pdChuYiRCZXN0Lm5jWzEsXSkpKQpgYGAKCiogSHViZXJ0IEluZGV4IGFuZCBEIEluZGV4IHN1Z2dlc3QgSyA9IDQgYXMgdGhlIGJlc3Qgc29sdXRpb24KCjxicj4KCioqKgoKPGJyPgoKIyBLLU1lYW5zIAoKPGJyPgoKIyMgQWdydXBhbWVudG8KCmBgYHtyfQpuX2NsdXN0ZXJzID0gMwoKc2NhbGVkX2RhdGEgJT4lCiAgICBrbWVhbnMobl9jbHVzdGVycywgaXRlci5tYXggPSAxMDAsIG5zdGFydCA9IDIwKSAtPiBrbQoKcCA8LSBhdXRvcGxvdChrbSwgZGF0YT1zY2FsZWRfZGF0YSwgZnJhbWUgPSBUUlVFKSAgCgpnZ3Bsb3RseShwKQoKYGBgCgoqIMOJIHBvc3PDrXZlbCB2ZXIgb3MgMyBncnVwb3Mgbml0aWRhbWVudGUgZGlzdGludG9zLCBwb3IgbWVpbyBkZSB1bSB6b29tIHBlcmNlYmUtc2UgcXVlIGVtYm9yYSBvIGdydXBvIDEgZSBvIGdydXBvIDMgZXN0ZWphbSBwcsOzeGltb3MgbyBvdmVybGFwIMOpIGJhc2ljYW1lbnRlIGluZXhpc3RlbnRlLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnJvdy5uYW1lcyhzY2FsZWRfZGF0YSkgPC0gZGF0YSR0aXRsZQoKdG9jbHVzdCA8LSBzY2FsZWRfZGF0YSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4odmFyID0gInRpdGxlIikgCgprbSA9IHRvY2x1c3QgJT4lIAogICAgc2VsZWN0KC10aXRsZSkgJT4lIAogICAga21lYW5zKGNlbnRlcnMgPSBuX2NsdXN0ZXJzLCBpdGVyLm1heCA9IDEwMCwgbnN0YXJ0ID0gMjApCgprbSAlPiUgCiAgICBhdWdtZW50KHRvY2x1c3QpICU+JSAKICAgIGdhdGhlcihrZXkgPSAidmFyacOhdmVsIiwgdmFsdWUgPSAidmFsb3IiLCAtdGl0bGUsIC0uY2x1c3RlcikgJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gYHZhcmnDoXZlbGAsIHkgPSB2YWxvciwgZ3JvdXAgPSB0aXRsZSwgY29sb3VyID0gLmNsdXN0ZXIpKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKyAKICAgIGdlb21fbGluZShhbHBoYSA9IC41KSArIAogICAgZmFjZXRfd3JhcCh+IC5jbHVzdGVyKSArCiAgICBjb29yZF9mbGlwKCkKCmBgYAoKPGJyPgoKKioqCgokXGNvbG9ye3JlZH17XHRleHR7R3J1cG8gMX19JCAtICoqV2UgQ2FuIERvIEl0ISoqCgogICogTWVub3IgRmF0dXJhbWVudG8gCiAgKiBNYWlzIGRpYWzDs2dvIHBhcmEgYXMgbXVsaGVyZXMgCiAgKiBNYWlvciB0YXhhIGRlIHBlcnNvbmFnZW5zIGZlbWluaW5vcwogICAgCjxicj4KCk8gJFxjb2xvcntyZWR9e1x0ZXh0e0dydXBvIDF9fSQgLSAqKldlIENhbiBEbyBJdCEqKiDDqSBvIGdydXBvIGRlIGZpbG1lcyBkZSBtYWlvciByZXByZXNlbnRhw6fDo28gZmVtaW5pbmEsIHF1ZXIgc2VqYSBlbSBwcm9wb3LDp8OjbyBkZSBwZXJzb25hZ2VucyBmZW1pbmlub3MgY29tbyBlbSBwcm9wb3LDp8OjbyBkZSBkaWFsw7Nnb3MgZGVkaWNhZG9zIGEgcGVyc29uYWdlbnMgZmVtaW5pbm9zLiBFeGlzdGUgcG9yw6ltIHVtYSBjYXJhY3RlcsOtc3RpY2EgbmVnYXRpdmEgcXVlIGFjb21wYW5oYSBlc3RlIG1lc21vIGdydXBvLCBwb2lzIGVzdGUgw6kgdGFtYsOpbSBvIGdydXBvIGRhcyBtZW5vcmVzIHRheGFzIGRlIGZhdHVyYW1lbnRvLiBJc3NvIHN1Z2VyZSB1bWEgaW5mZWxpeiBhc3NvY2lhw6fDo28gbmVnYXRpdmEgZW50cmUgYSByZXByZXNlbnRhw6fDo28gZmVtaW5pbmEgZW0gZmlsbWVzIGUgbyBmYXR1cmFtZW50byBkZXN0ZXMuIAoKPGJyPgoKCmBgYApPIG5vbWUgZG8gZ3J1cG8gc2UgcmVmZXJlIGFvIGZhbW9zbyBjYXJ0YXogZGUgSi4gSG93YXJkIE1pbGxlciBkZSAxOTQzIGluY2VudGl2YWRvIGFzIG11bGhlcmVzIGEgcGFydGljaXBhciBubyBlc2ZvcsOnbyBkZSBndWVycmEgbmFzIGbDoWJyaWNhcy4gCmBgYAoKPGJyPgoKKioqCgokXGNvbG9ye2dyZWVufXtcdGV4dHtHcnVwbyAyfX0kIC0gKipJdCdzIEEgTWFuJ3MgTWFuJ3MgTWFuJ3MgV29ybGQqKiAgIAoKKiBNYWlvciBmYXR1cmFtZW50byBlbnRyZSB0b2RvcwoqIE1lbm9yIHRheGEgZGUgZGlhbMOzZ28gcGFyYSBhcyBtdWxoZXJlcwoqIE1lbm9yIHRheGEgZGUgcGVyc29uYWdlbnMgZmVtaW5pbm9zCiAgICAKPGJyPgoKTyAkXGNvbG9ye2dyZWVufXtcdGV4dHtHcnVwbyAyfX0kIC0gKipJdCdzIEEgTWFuJ3MgTWFuJ3MgTWFuJ3MgV29ybGQqKiDDqSBvIGdydXBvIGRlIGZpbG1lcyBkZSBtZW5vciByZXByZXNlbnRhw6fDo28gZmVtaW5pbmEsIHF1ZXIgc2VqYSBlbSBwcm9wb3LDp8OjbyBkZSBwZXJzb25hZ2VucyBmZW1pbmlub3MgY29tbyBlbSBwcm9wb3LDp8OjbyBkZSBkaWFsw7Nnb3MgZGVkaWNhZG9zIGEgcGVyc29uYWdlbnMgZmVtaW5pbm9zLiBFeGlzdGUgcG9yw6ltIHVtYSBjYXJhY3RlcsOtc3RpY2EgbmVnYXRpdmEgcXVlIGFjb21wYW5oYSBlc3RlIG1lc21vIGdydXBvLCBwb2lzIGVzdGUgw6kgdGFtYsOpbSBvIGdydXBvIGRlIG1haW9yZXMgdGF4YXMgZGUgZmF0dXJhbWVudG8uIElzc28gc3VnZXJlIHVtYSBpbmZlbGl6IGFzc29jaWHDp8OjbyBwb3NpdGl2YSBlbnRyZSBhdXPDqm5jaWEgZGUgIHJlcHJlc2VudGHDp8OjbyBmZW1pbmluYSBlbSBmaWxtZXMgZSBvIGZhdHVyYW1lbnRvIGRlc3Rlcy4gCgo8YnI+CgpgYGAKTyBub21lIGRvIGdydXBvIHNlIHJlZmVyZSDDoCBtw7pzaWNhIGRlIEphbWVzIEJyb3duLCBhIHF1YWwgZm9pIGVzY3JpdGEgcG9yIHN1YSBlbnTDo28gbmFtb3JhZGEgQmV0dHkgSmVhbiBOZXdzb21lIGNvbW8gdW0gY29tZW50w6FyaW8gc29icmUgYSByZWxhw6fDo28gZW50cmUgb3Mgc2V4b3MuCmBgYAoKPGJyPgoKKioqCgokXGNvbG9ye2JsdWV9e1x0ZXh0e0dydXBvIDN9fSQgLSAqKkVtIGNpbWEgZG8gbXVybyoqCgogICogRmlsbWVzIG1lZGlhbm9zIGVtIHRlcm1vcyBkZSBwcm9wb3LDp8OjbyBkZSBwZXJzb25hZ2VucyBmZW1pbmlub3MsIHByb3BvcsOnw6NvIGRlIGRpYWzDs2dvcyBkZWRpY2Fkb3MgYSBwZXJzb25hZ2VucyBmZW1pbmlub3MgZSBmYXR1cmFtZW50by4KICAgIAo8YnI+CgpgYGAKTyBub21lIGRvIGdydXBvIHNlIHJlZmVyZSDDoCBleHByZXNzw6NvIHF1ZSBzaWduaWZpY2EgbsOjbyB0b21hciBwYXJ0aWRvLgpgYGAKCjxicj4KCiMjIFF1YWxpZGFkZSBkYSBjbHVzdGVyaXphw6fDo28gLyBTaWxodWV0YQoKYGBge3J9CmRpc3RzID0gc2NhbGVkX2RhdGEgJT4lIAogIGRpc3QoKQoKc2NhbGVkX2RhdGEgJT4lCiAgICBrbWVhbnMoMywgaXRlci5tYXggPSAxMDAsIG5zdGFydCA9IDIwKSAtPiBrbQoKCnNpbGhvdWV0dGUoa20kY2x1c3RlciwgZGlzdHMpICU+JQogICBwbG90KGNvbCA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg0LCAiU2V0MiIpLGJvcmRlcj1OQSkKYGBgCgo8YnI+CgoqIE8gdmFsb3IgZGUgMC42IGRhIHNpbGh1ZXRhIHNpZ25pZmljYSBxdWUgYSBub3NzYSBjbHVzdGVyaXphw6fDo28gZm9pIHJhem/DoXZlbC4g44O+KOKMkOKWoF/ilqAp44OO4pmqCgo=